#pragma once

#include <ode/ode.h>
#include <irrlicht.h>

#include "box.h"

namespace dalelib
{

class IrrlichtWindow
{
protected:

	/*** stuff ***/
	int _width;
	int _height;
	bool _fullscreen;
	typedef vector<Box> BoxList;
	BoxList _boxes;

	/*** Irrlicht stuff ***/
	IrrlichtDevice *_device;
	IVideoDriver* _driver;
	ISceneManager* _scene_manager;
	ICameraSceneNode* _camera;

	/*** ODE stuff ***/
	dWorldID _world;
	dSpaceID _space;
	dJointGroupID _contactgroup;

public:

	static IrrlichtWindow* pointer;

	IrrlichtWindow() { }
	~IrrlichtWindow() { }

	void cleanup() 
	{
		dWorldDestroy(_world);
		dCloseODE();
		_device->drop(); 
	}

	dWorldID getWorld() { return _world; }
	dSpaceID getSpace() { return _space; }
	dJointGroupID getContactGroup() { return _contactgroup; }

	IrrlichtDevice* getDevice() { return _device; }
	IVideoDriver* getDriver() { return _driver; }
	ISceneManager* getSceneManager() { return _scene_manager; }
	ICameraSceneNode* getCamera() { return _camera; }

	void init( 
		const std::wstring _Name=L"IrrlichtWindow", 
		const int _Width=256, 
		const int _Height=256, 
		const bool _Fullscreen=false, 
		const bool _Stencil=false )
	{
		IrrlichtWindow::pointer = this;

		_width = _Width;
		_height = _Height;
		_fullscreen = _Fullscreen;

		_device = createDevice(video::EDT_OPENGL, core::dimension2d<s32>(_width, _height), 16, _fullscreen, _Stencil); 
		if(_device==0) { /* todo: error no device */ }
		_driver = _device->getVideoDriver();
		_scene_manager = _device->getSceneManager();
		_device->setWindowCaption( _Name.c_str() );

		/*** irrlicht setup ***/
		_camera = _scene_manager->addCameraSceneNodeFPS( 0, 80.f, 40.f, -1, 0, 0, false, 0.f );
		_camera->setPosition( vector3df( 0.f, 5.f, -15.f ) );
		_camera->setTarget( vector3df( 0.f, 0.f, 0.f ) );
		ILightSceneNode* light = _scene_manager->addLightSceneNode( 0, core::vector3df(40, 100, -40), video::SColorf(1.0f, 1.0f, 1.0f), 1000.0f );
		_device->getCursorControl()->setVisible(false); // hide mouse
		_driver->setFog( video::SColor(0,255,255,255), true, 100, 500, 0, true);
		_scene_manager->setShadowColor(video::SColor(120,0,0,0));

		/*** ODE setup ***/
		_world = dWorldCreate();
		_space = dHashSpaceCreate( 0 );
		_contactgroup = dJointGroupCreate( 0 );
		dWorldSetGravity( _world, 0, -10.0, 0 );
		dWorldSetCFM( _world, 1e-5f );
		dWorldSetERP( _world, 0.2f );
		dWorldSetAutoDisableFlag( _world, 1 );

		/*** ground ***/
		scene::IAnimatedMesh* ground_mesh = _scene_manager->addHillPlaneMesh("ground_plane",
			core::dimension2d<f32>(1000,1000),
			core::dimension2d<u32>(1,1), 0, 0,
			core::dimension2d<f32>(0,0),
			core::dimension2d<f32>(100,100));
		IAnimatedMeshSceneNode* node = _scene_manager->addAnimatedMeshSceneNode( ground_mesh );
		node->setMaterialTexture(0,	_driver->getTexture("ground.jpg") );
		node->setMaterialType( video::EMT_REFLECTION_2_LAYER );
		node->setMaterialFlag( EMF_LIGHTING, false );
		node->setMaterialFlag( EMF_FOG_ENABLE, true );
		dCreatePlane( _space, 0, 1, 0, -0.1 );

		setup(); // user setup callback
	}

	void run()
	{ 
		while( _device->run() )
		{
			update(); // user update callback
			
			for(std::vector<Box>::iterator i=_boxes.begin(); i!=_boxes.end(); ++i) (*i).update(); // update boxes

			/*** ode step ***/
			dSpaceCollide( _space, 0, &nearCallback ); 
			dWorldQuickStep( _world, 0.025f );
			dJointGroupEmpty( _contactgroup );	

			/*** irrlicht draw ***/
			_driver->beginScene(true, true, video::SColor(255,255,255,255));
	//glEnable(GL_POLYGON_OFFSET_FILL);
	//glPolygonOffset(100.0f, -100.0f); 
			_scene_manager->drawAll();
			draw(); // user draw callback
			_driver->endScene();
		}
		setdown(); // user setdown callback
		cleanup();
	}

	static void nearCallback(void* node, dGeomID o1, dGeomID o2)
	{
		int i=0;
		dBodyID b1 = dGeomGetBody( o1 );
		dBodyID b2 = dGeomGetBody( o2 );
		const int MAX_CONTACTS = 4;
		if(b1 && b2 && dAreConnectedExcluding(b1,b2,dJointTypeContact))return;
		dContact contact[MAX_CONTACTS];
		for(i=0; i<MAX_CONTACTS; ++i)
		{
			contact[i].surface.mode=dContactBounce | dContactSoftCFM;
			contact[i].surface.mu=dInfinity;
			contact[i].surface.mu2=0;
			contact[i].surface.bounce=.3f;
			contact[i].surface.bounce_vel=.1f;
			contact[i].surface.soft_cfm=.001f;
		}
		int numc = dCollide( o1, o2, MAX_CONTACTS, &contact[0].geom, sizeof(dContact) );
		if(numc>0)
		{
			for(i=0;i<numc;i++)
			{
				dJointID c = dJointCreateContact( IrrlichtWindow::pointer->getWorld(), IrrlichtWindow::pointer->getContactGroup(), &contact[i]);
				dJointAttach(c,b1,b2);
			}
		}
	}

	void addBox( const vector3df _Size, const vector3df _Pos )
	{
		IAnimatedMesh* mesh = _scene_manager->getMesh( "box.x" );
		_boxes.push_back( Box( _world, _space, _scene_manager, mesh, _Size, _Pos ) );
	}

	virtual void setup() = 0;
	virtual void setdown() = 0;
	virtual void update() = 0;
	virtual void draw() = 0;

};

} // namespace dalelib

dalelib::IrrlichtWindow* dalelib::IrrlichtWindow::pointer = 0;
